﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Model;

namespace ReportBrowser.ViewModel
{
    internal class ModuleViewModel : LanguageElementViewModelBase
    {
        #region Fields

        private static Dictionary<Module, ModuleViewModel> sCallerInstances =
            new Dictionary<Module, ModuleViewModel>();
        private static Dictionary<Module, ModuleViewModel> sCalleeInstances =
            new Dictionary<Module, ModuleViewModel>();

        #endregion Fields

        #region Lifecycle

        static ModuleViewModel()
        {
            MessageViewModel.InstanceSelected += new System.EventHandler(MessageViewModel_InstanceSelected);
        }

        public ModuleViewModel(Module model, bool isTruncatedAsCaller = false, bool isTruncatedAsCallee = false)
        {
            this.Model = model;
            this.Callees = new ObservableCollection<ModuleViewModel>();
            this.Callers = new ObservableCollection<ModuleViewModel>();
        }
        public static ModuleViewModel GetInstance(Module model, bool isCaller)
        {
            if (isCaller == true)
            {
                if (sCallerInstances.ContainsKey(model) == false)
                    sCallerInstances.Add(model, new ModuleViewModel(model));
                return sCallerInstances[model];
            }
            else if (isCaller == false)
            {
                if (sCalleeInstances.ContainsKey(model) == false)
                    sCalleeInstances.Add(model, new ModuleViewModel(model));
                return sCalleeInstances[model];
            }
            else
                throw new System.ArgumentException();
        }

        public static bool IsInstantiated(Module model, bool isCaller,
            bool isTruncatedAsCaller, bool isTruncatedAsCallee)
        {
            if (isCaller == true)
                return sCallerInstances.ContainsKey(model);
            else if (isCaller == false)
                return sCalleeInstances.ContainsKey(model);
            else
                throw new System.ArgumentException();
        }

        private static void ClearInstances()
        {
            sCallerInstances = new Dictionary<Module, ModuleViewModel>();
            sCalleeInstances = new Dictionary<Module, ModuleViewModel>();
        }

        #endregion Lifecycle

        #region Properties

        public Module Model { get; private set; }

        private bool mIsCallersInitialized = false;
        private ObservableCollection<ModuleViewModel> pCallers;
        public ObservableCollection<ModuleViewModel> Callers
        {
            get
            {
                if (mIsCallersInitialized == false)
                {
                    foreach (var item in this.Model.CallGraphEdge_ModuleDependency_Callee)
                    {
                        if (item.CallerModule != null)
                            pCallers.Add(GetInstance(item.CallerModule, isCaller: true));
                        else
                            Debug.WriteLine("(item in CallGraphEdge_ModuleDependency_Callee).CallerModule == null");
                    }
                    mIsCallersInitialized = true;
                }
                return pCallers;
            }
            private set
            {
                if (value != pCallers)
                {
                    pCallers = value;
                    OnPropertyChanged("Callers");
                }
            }
        }

        private bool mIsCalleesInitialized = false;
        private ObservableCollection<ModuleViewModel> pCallees;
        public ObservableCollection<ModuleViewModel> Callees
        {
            get
            {
                if (mIsCalleesInitialized == false)
                {
                    foreach (var item in this.Model.CallGraphEdge_ModuleDependency_Caller)
                    {

                        if (item.CalleeModule != null)
                            pCallees.Add(GetInstance(item.CalleeModule, isCaller: false));
                        else
                            Debug.WriteLine("(item in CallGraphEdge_ModuleDependency_Caller).CalleeModule == null");
                    }
                    mIsCalleesInitialized = true;
                }
                return pCallees;
            }
            private set
            {
                if (value != pCallees)
                {
                    pCallees = value;
                    OnPropertyChanged("Callees");
                }
            }
        }

        #endregion Properties

        #region Methods

        /*
        private bool IsOnCycle(Module module, bool asCaller)
        {
            HashSet<Module> gray = new HashSet<Module>();
            HashSet<Module> black = new HashSet<Module>();
            Debug.WriteLine("IsOnCycle: module=" + module.Name + " asCaller=" + asCaller.ToString());
            List<Module> path = DFS(module, asCaller, ref gray, ref black);
            if (path != null)
                Debug.WriteLine("path==<" + SetToString(path) + ">");
            return path != null;
        }

        private List<Module> DFS(Module module, bool asCaller,
            ref HashSet<Module> gray, ref HashSet<Module> black)
        {
            gray.Add(module);
            EntityCollection<CallGraphEdge_ModuleDependency> neighbours;
            if (asCaller == true)
                neighbours = module.CallGraphEdge_ModuleDependency_Caller;
            else
                neighbours = module.CallGraphEdge_ModuleDependency_Callee;
            foreach (var item in neighbours)
            {
                Module neighbour;
                if (asCaller == true)
                    neighbour = item.CalleeModule;
                else
                    neighbour = item.CallerModule;
                if ((asCaller == true
                    && (ModuleViewModel.IsInstantiated(neighbour, isCaller: !asCaller,
                                isTruncatedAsCaller: false, isTruncatedAsCallee: false) == true
                            || ModuleViewModel.IsInstantiated(neighbour, isCaller: !asCaller,
                                isTruncatedAsCaller: true, isTruncatedAsCallee: false) == true))
                    || (asCaller == false
                        && (ModuleViewModel.IsInstantiated(neighbour, isCaller: !asCaller,
                                isTruncatedAsCaller: false, isTruncatedAsCallee: false) == true
                            || ModuleViewModel.IsInstantiated(neighbour, isCaller: !asCaller,
                                isTruncatedAsCaller: false, isTruncatedAsCallee: true) == true)))
                {
                    if (gray.Contains(neighbour) == false && black.Contains(neighbour) == false)//white
                    {
                        List<Module> path = DFS(neighbour, asCaller, ref gray, ref black);
                        if (path != null)
                        {
                            path.Add(neighbour);
                            return path;
                        }
                    }
                    else if (gray.Contains(neighbour) == true && black.Contains(neighbour) == false)//gray
                    {
                        return new List<Module>() { neighbour };
                    }
                }
            }
            gray.Remove(module);
            black.Add(module);
            return null;
        }

        private string SetToString(IEnumerable<Module> set)
        {
            string s = "";
            foreach (var item in set)
            {
                s += item.Name + ", ";
            }
            return s.TrimEnd(new char[] { ' ', ',' });
        }
        */

        #endregion Methods

        #region EventHandlers

        static void MessageViewModel_InstanceSelected(object sender, System.EventArgs e)
        {
            ClearInstances();
        }

        #endregion EventHandlers
    }
}
